import json
import math
import time
import uuid

import redis
from redis.client import Redis

mconn = Redis(host="192.168.35.113", port=6379, db=0, decode_responses=True)


def acquire_lock_with_timeout(conn, lockname, acquire_timeout=10, lock_timeout=10):
    identifier = str(uuid.uuid4())
    lockname = 'lock:' + lockname
    lock_timeout = int(math.ceil(lock_timeout))
    end = time.time() + acquire_timeout
    while time.time() < end:
        if conn.setnx(lockname, identifier):
            conn.expire(lockname, lock_timeout)
            return identifier
        elif not conn.ttl(lockname):
            conn.expire(lockname, lock_timeout)
        time.sleep(0.001)
    return False


def release_lock(conn, lockname, identifier):
    pipe = conn.pipeline(True)
    lockname = 'lock:' + lockname
    while True:
        try:
            pipe.watch(lockname)
            if pipe.get(lockname) == identifier:
                pipe.multi()
                pipe.delete(lockname)
                pipe.execute()
            pipe.unwatch()
            break
        except redis.exceptions.WatchError:
            pass
    return False


def create_chat(conn, sender, recipients, message, chat_id=None):
    chat_id = chat_id or str(conn.incr('ids:chat:'))
    recipients.append(sender)
    recipientsd = dict((r, 0) for r in recipients)

    pipe = conn.pipeline(True)
    pipe.zadd('chat:' + chat_id, recipientsd)
    for rec in recipients:
        pipe.zadd('seen:' + rec, {chat_id: 0})
    pipe.execute()
    return send_message(conn, chat_id, sender, message)


def send_message(conn, chat_id, sender, message):
    identifier = acquire_lock_with_timeout(conn, 'chat:' + chat_id)
    if not identifier:
        raise Exception("Counldn`t get the lock")
    try:
        mid = conn.incr('ids:' + chat_id)
        ts = time.time()
        packed = json.dumps({
            'id': mid,
            'ts': ts,
            'sender': sender,
            'message': message
        })
        conn.zadd('msgs:' + chat_id, {packed: mid})
    finally:
        release_lock(conn, 'chat:' + chat_id, identifier)
    return chat_id


def fetch_pending_message(conn, recipient):
    seen = conn.zrange('seen:' + recipient, 0, -1, withscores=True)
    pipe = conn.pipeline(True)
    target = []
    for chat_id, seen_id in seen:
        pipe.zrangebyscore('msgs:' + chat_id, seen_id + 1, 'inf')
    chat_info = zip(seen, pipe.execute())
    for i, ((chat_id, seen_id), message) in enumerate(chat_info):
        if not message:
            continue
        message[:] = map(json.loads, message)
        seen_id = message[-1]['id']
        conn.zadd('chat:' + chat_id, {recipient: seen_id})
        min_id = conn.zrange('chat:' + chat_id, 0, 0, withscores=True)
        pipe.zadd('seen:' + recipient, {chat_id: seen_id})
        if min_id:
            pipe.zremrangebyscore('msgs:' + chat_id, 0, min_id[0][1])
        target.append({chat_id: message})
    pipe.execute()
    return target


def join_chat(conn, chat_id, user):
    message_id = int(conn.get('ids:' + chat_id))
    pipe = conn.pipeline(True)
    pipe.zadd('chat:' + chat_id, {user: message_id})
    pipe.zadd('seen:' + user, {chat_id: message_id})
    pipe.execute()


def leave_chat(conn, chat_id, user):
    pipe = conn.pipeline(True)
    pipe.zrem('chat:' + chat_id, user)
    pipe.zrem('seen:' + user, chat_id)
    pipe.zcard('chat:' + chat_id)
    if not pipe.execute()[-1]:
        pipe.delete('msgs' + chat_id)
        pipe.delete('ids:' + chat_id)
        pipe.execute()
    else:
        oldest = conn.zrange('chat:' + chat_id, 0, 0, withscores=True)
        conn.zremrangebyscore('msgs:' + chat_id, 0, oldest[0][1])


def print_hi(name):
    # 在下面的代码行中使用断点来调试脚本。
    print(f'Hi, {name}')  # 按 Ctrl+F8 切换断点。


# 按间距中的绿色按钮以运行脚本。
if __name__ == '__main__':
    # create_chat(mconn, 'hyman', ['tom', 'jerry'], '会议结束了', '3')
    chat_info = fetch_pending_message(mconn, 'hyman')
    join_chat(mconn, 3, 'lilei')
    leave_chat(mconn, 3, 'lilei')
    print(chat_info)
    print_hi('PyCharm')

# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助
